/**
 * descriptor这个对象里包含对装饰对象的描述属性
 * configurable: true/false, 可配置与否
 * enumerable: true/false, 可枚举与否
 * writable: true/false, 可写与否
 * initializer: 静态属性的value值
 * value: 非静态属性的value值
 */

/**********************一些输出**********************/
function log(str, ...args) {
    console.log(`[decorators] [log] ${str}`, ...args);
}

function warn(str, ...args) {
    console.warn(`[decorators] [warn] ${str}`, ...args);
}

function error(str, ...args) {
    console.error(`[decorators] [error] ${str}`, ...args);
}

/**********************自动绑定节点**********************/
/**
 * 遍历子孙节点
 * @param {cc.Node} node 
 * @param {(node)=>Boolean} cb  返回false结束遍历
 */
function forEachChildren(node, cb) {
    if (!node || !cb) {
        return null;
    }

    const children = node.children;
    if (children.length) {
        // 广度优先
        for (let index = 0; index < children.length; index++) {
            if (cb(children[index]) === false) {
                return false;
            }
        }
        // 深度其次
        for (let index = 0; index < children.length; index++) {
            if (forEachChildren(children[index], cb) === false) {
                return false;
            }
        }
    }
}

/**
 * 查找所在节点的所有子孙节点
 * @template T
 * @param {cc.Node} parent          要查询的节点
 * @param {String} name             节点名字
 * @param {{prototype: T}} type     指定节点上的某个组件
 * @param {Boolean} multiple        是否查询多个
 * @return {T}
 * 
 */
function find(parent, name, type, multiple) {
    if (!parent || !name) {
        return [];
    }

    const node = parent instanceof cc.Node ? parent : parent.node;
    if (!node.children.length) {
        return [];
    }

    let res = null;
    const result = [];
    forEachChildren(node, function (child) {
        res = (child.name === name) && ((!type || type === cc.Node) ? child : child.getComponent(type));
        if (res) {
            result.push(res);
            if (!multiple) {
                return false;
            }
        }
    });

    return result;
}

function classOf(param) {
    return toString.call(param).slice(8, -1);
}
function isArray(param) {
    return classOf(param) === 'Array';
}
function isMap(param) {
    return param !== null && typeof param === 'object' && !isArray(param);
}
function isEmpty(param) {
    return isArray(param) ? !param.length : (param === null || param === undefined);
}

// 缓存key
const autoPropertyCacheKey = '_$auto_property_attr_cache$';

// 编辑器下检查缓存的节点是否有效
const editorCheckCache = function (cache, nodeName) {
    if (!cache) {
        return false;
    }
    if (cache instanceof Array) {
        return cache.every(item => item && cc.isValid(item.node || item) && (item.node || item).name === nodeName);
    } else {
        return cache && cc.isValid(cache.node || cache) && (cache.node || cache).name === nodeName;
    }
};

function _autoProperty(...args) {
    const [prototype, key, descriptor = {}, type = cc.Node, parent, multiple = false] = args;

    const { get, set } = descriptor;
    const isGetSet = !!get && !!set;

    if (CC_DEBUG) {
        if (descriptor.value) {
            warn(`[autoProperty] {${cc.js.getClassName(prototype)}.${key}}是一个function, 这将导致它会被重新定义`);
        }
        if (!isGetSet && (get || set)) {
            warn(`[autoProperty] {${cc.js.getClassName(prototype)}.${key}}的get和set必须成对出现`);
        }
    }

    // 返回一个新的descriptor
    const newDescriptor = {
        configurable: descriptor.configurable,
        enumerable: descriptor.enumerable,
        // writable: descriptor.writable,
        get() {
            // 节点名字
            const nodeName = '$' + key;
            // 查缓存
            if (isGetSet) {
                const result = get.call(this);
                if (CC_EDITOR) {
                    if (editorCheckCache(result, nodeName)) return result;
                } else {
                    return result;
                }
            } else if (this[autoPropertyCacheKey] && !isEmpty(this[autoPropertyCacheKey][nodeName])) {
                const result = this[autoPropertyCacheKey][nodeName];
                if (CC_EDITOR) {
                    if (editorCheckCache(result, nodeName)) return result;
                } else {
                    return result;
                }
            }
            // 节点树查询
            const node = parent ? (typeof parent === 'function' ? parent.call(this, this) : this[parent]) : this.node;
            const query = find(node, nodeName, type, multiple);
            if (query.length === 0) { warn(`[autoProperty] [${this.node.name}] 未查询到名字为{${nodeName}}并带有{${type.name || type}}组件的节点`); }
            const result = multiple ? query : (query[0] || null);
            // 缓存结果
            if (query.length) {
                if (isGetSet) {
                    set.call(this, result);
                    return get.call(this);
                } else {
                    if (!this[autoPropertyCacheKey]) Object.defineProperty(this, autoPropertyCacheKey, { value: {} });
                    this[autoPropertyCacheKey][nodeName] = result;
                }
            }
            // 返回结果
            return result;
        },
        set(value) {
            if (!CC_EDITOR || value === null) {
                if (isGetSet) {
                    set.call(this, value);
                } else {
                    if (!this[autoPropertyCacheKey]) Object.defineProperty(this, autoPropertyCacheKey, { value: {} });
                    this[autoPropertyCacheKey]['$' + key] = value;
                }
            }
        }
    };

    // 在编辑面板中显示
    if (CC_EDITOR) {
        const cctype = typeof type === 'string' ? (cc.js.getClassByName(type)) : type;
        cc._decorator.property({ type: multiple ? [cctype] : cctype, tooltip: `自动在子孙节点中搜索\n名字: $${key}\n组件: ${typeof type === 'string' ? type : cc.js.getClassName(type)}` })(prototype, key, newDescriptor);
    }

    return newDescriptor;
}
/**
 * @description 
 * 节点名字只有[以$开头]才能搜索到，声明变量时可以不用$开头，
 * 默认查询节点下的cc.Node组件，可以自定义，
 * 默认返回单个结果，如果自定义为数组形式的话会返回多个符合条件的结果
 * 
 * 例:
 * @autoProperty 或者 @autoProperty(cc.Node)
 * face1: cc.Node = null;
 * this.face1 根据查询结果可能是个node也可能是null
 * 
 * 
 * @autoProperty([]) 或者 @autoProperty([cc.Node])
 * face2: cc.Node[] = [];
 * this.face2 根据查询结果可能是个node的数组也可能是[]
 * 
 * @autoProperty(cc.Sprite)
 * face3: cc.Sprite = null;
 * this.face3 根据查询结果可能是个sprite组件也可能是null
 * 
 * @autoProperty([cc.Sprite])
 * face4: cc.Sprite[] = [];
 * this.face4 根据查询结果可能是个sprite组件的数组也可能是[]
 *  
 */
export function autoProperty(options?: { type?: typeof cc.Object | [typeof cc.Object] | string | [string], parent?: string | ((self: any) => any) } | typeof cc.Object | [typeof cc.Object] | string | [string]): Function;
export function autoProperty(prototype: Object, key: string, descriptor?: any): any;
export function autoProperty(...args): any {
    // 传入自定义参数
    if (args.length <= 1) {
        let param = args[0];

        let type = null;
        let parent = null;
        let multiple = false;

        if (isMap(param)) {
            type = param.type;
            parent = param.parent;
        } else {
            type = param;
        }

        if (isArray(type)) {
            type = type[0] || cc.Node;
            multiple = true;
        }

        return function (prototype, key, descriptor) {
            return _autoProperty(prototype, key, descriptor, type, parent, multiple);
        };
    } else {
        return _autoProperty(...args);
    }
}

/**
 * 删除自动绑定的数据缓存,key为空会删除com下的全部
 */
export function delAutoPropertyCache<T extends cc.Component>(com: T, key?: string) {
    if (com && com[autoPropertyCacheKey]) {
        if (!key) {
            cc.js.clear(com[autoPropertyCacheKey]);
        } else if (com[autoPropertyCacheKey]) {
            delete com[autoPropertyCacheKey]['$' + key];
        }
    }
}